package org.flintparticles.common.stage3d
{
	import flash.geom.Matrix3D;
	import flash.geom.Point;
	import flash.geom.Vector3D;
	
	import org.flintparticles.common.stage3d.utils.Color;
	
	public class Vao
	{
		//@SasMaster:ported by SasMaster from the Starling with some minor modifications///
		/** The total number of elements (Numbers) stored per vertex. */
		public static const ELEMENTS_PER_VERTEX:int = 9;
		
		/** The offset of position data (x, y) within a vertex. */
		public static const POSITION_OFFSET:int = 0;
		
		/** The offset of color data (r, g, b, a) within a vertex. */ 
		public static const COLOR_OFFSET:int = 3;
		
		/** The offset of texture coordinate (u, v) within a vertex. */
		public static const TEXCOORD_OFFSET:int = 7;
		
		private var _data:Vector.<Number>;
		private var _premultipliedAlpha:Boolean;
		public function Vao(numVertices:int, premultipliedAlpha:Boolean=false)
		{
			_data = new Vector.<Number>(numVertices * ELEMENTS_PER_VERTEX, true);
			_premultipliedAlpha = premultipliedAlpha;
		}
		/** Appends the vertices from another Vao object. */
		public function append(vao:Vao):void
		{
			data.fixed = false;
			
			for each (var element:Number in vao.data)
			data.push(element);
			
			data.fixed = true;
		}
		
		// functions
		
		/** Updates the position values of a vertex. */
		public function setPosition(vertexID:int, x:Number, y:Number, z:Number=0.0):void
		{
			setValues(getOffset(vertexID) + POSITION_OFFSET, x, y, z);
		}
		
		/** Returns the position of a vertex. */
		public function getPosition(vertexID:int):Vector3D
		{
			var offset:int = getOffset(vertexID) + POSITION_OFFSET;
			return new Vector3D(data[offset], data[offset+1], data[offset+2]);
		}
		
		/** Updates the color and alpha values of a vertex. */ 
		public function setColor(vertexID:int, color:uint, alpha:Number=1.0):void
		{
			var multiplier:Number = _premultipliedAlpha ? alpha : 1.0;
			setValues(getOffset(vertexID) + COLOR_OFFSET, 
				Color.getRed(color)   / 255.0 * multiplier,
				Color.getGreen(color) / 255.0 * multiplier,
				Color.getBlue(color)  / 255.0 * multiplier,
				alpha);
			
		}
		
		/** Returns the RGB color of a vertex (no alpha). */
		public function getColor(vertexID:int):uint
		{
			var offset:int = getOffset(vertexID) + COLOR_OFFSET;
			var divisor:Number = _premultipliedAlpha ? data[offset+3] : 1.0;
			
			if (divisor == 0) return 0;
			else
			{
				var red:Number   = data[offset  ] / divisor;
				var green:Number = data[offset+1] / divisor;
				var blue:Number  = data[offset+2] / divisor;
				return Color.rgb(red * 255, green * 255, blue * 255);
			}
		}
		
		/** Updates the alpha value of a vertex (range 0-1). */
		public function setAlpha(vertexID:int, alpha:Number):void
		{
			if (_premultipliedAlpha) setColor(vertexID, getColor(vertexID), alpha);
			else 
			{
				var offset:int = getOffset(vertexID) + COLOR_OFFSET + 3;
				data[offset] = alpha;
			}
		}
		
		/** Returns the alpha value of a vertex in the range 0-1. */
		public function getAlpha(vertexID:int):Number
		{
			var offset:int = getOffset(vertexID) + COLOR_OFFSET + 3;
			return data[offset];
		}
		
		/** Updates the texture coordinates of a vertex (range 0-1). */
		public function setTexCoords(vertexID:int, u:Number, v:Number):void
		{
			setValues(getOffset(vertexID) + TEXCOORD_OFFSET, u, v);
		}
		
		/** Returns the texture coordinates of a vertex in the range 0-1. */
		public function getTexCoords(vertexID:int):Point
		{
			var offset:int = getOffset(vertexID) + TEXCOORD_OFFSET;
			return new Point(data[offset], data[offset+1]);
		}
		
		/** Creates a duplicate of the vertex data object. */
		public function clone():Vao
		{
			var clone:Vao = new Vao(0, _premultipliedAlpha);
			clone._data = _data.concat();
			clone._data.fixed = true;
			return clone;
		}
		
		// utility functions
		
		/** Translate the position of a vertex by a certain offset. */
		public function translateVertex(vertexID:int, 
										deltaX:Number, deltaY:Number, deltaZ:Number=0.0):void
		{
			var offset:int = getOffset(vertexID) + POSITION_OFFSET;
			_data[offset]   += deltaX;
			_data[offset+1] += deltaY;
			_data[offset+2] += deltaZ;
		}
		
		/** Transforms the position of a vertex by multiplication with a transformation matrix. */
		public function transformVertex(vertexID:int, matrix:Matrix3D=null):void
		{
			var position:Vector3D = getPosition(vertexID);
			
			if (matrix)
			{
				var transPosition:Vector3D = matrix.transformVector(position);
				setPosition(vertexID, transPosition.x, transPosition.y, transPosition.z);
			}
		}
		
		/** Sets all vertices of the object to the same color and alpha values. */
		public function setUniformColor(color:uint, alpha:Number=1.0):void
		{
			for (var i:int=0; i<numVertices; ++i)
				setColor(i, color, alpha);
		}
		
		/** Multiplies the alpha value of a vertex with a certain delta. */
		public function scaleAlpha(vertexID:int, alpha:Number):void
		{
			if (_premultipliedAlpha) setAlpha(vertexID, getAlpha(vertexID) * alpha);
			else
			{
				var offset:int = getOffset(vertexID) + COLOR_OFFSET + 3;
				_data[offset] *= alpha;
			}
		}
		
		private function setValues(offset:int, ...values):void
		{
			var numValues:int = values.length;
			for (var i:int=0; i<numValues; ++i)
				_data[offset+i] = values[i];
		}
		
		private function getOffset(vertexID:int):int
		{
			return vertexID * ELEMENTS_PER_VERTEX;
		}
		
		// properties
		
		/** Changes the way alpha and color values are stored. Updates all exisiting vertices. */
		public function set premultipliedAlpha(value:Boolean):void
		{
			if (value == _premultipliedAlpha) return;            
			var dataLength:int = data.length;
			
			for (var i:int=COLOR_OFFSET; i<dataLength; i += ELEMENTS_PER_VERTEX)
			{
				var alpha:Number = data[i+3];
				var divisor:Number = _premultipliedAlpha ? alpha : 1.0;
				var multiplier:Number = value ? alpha : 1.0;
				
				if (divisor != 0)
				{
					_data[i  ] = data[i  ] / divisor * multiplier;
					_data[i+1] = data[i+1] / divisor * multiplier;
					_data[i+2] = data[i+2] / divisor * multiplier;
				}
			}
			
			_premultipliedAlpha = value;
		}
		
		/** Indicates if the rgb values are stored premultiplied with the alpha value. */
		public function get premultipliedAlpha():Boolean { return _premultipliedAlpha; }
		
		/** The total number of vertices. */
		public function get numVertices():int { return data.length / ELEMENTS_PER_VERTEX; }
		
		/** The raw vertex data; not a copy! */
		public function get data():Vector.<Number> { return _data; }
	}
}
